using System;
using Microsoft.Samples.ServiceHosting.StorageClient;
using SecUtility = Microsoft.Samples.ServiceHosting.AspProviders.SecUtility;
using Log = Microsoft.Samples.ServiceHosting.HelloFabric.ApplicationEnvironment;

namespace BlogEngine.Core.Azure.BlogProvider.Tables
{
    public abstract class Row : TableStorageEntity
    {
        public abstract string GetRowKey();

        public static string ToRowKey(Guid id)
        {
            return id.ToString();
        }

        public void CopyFrom<TRow>(TRow row)
            where TRow : Row
        {
            var type = typeof(TRow);
            foreach (var propertyInfo in type.GetProperties())
                propertyInfo.SetValue(this, propertyInfo.GetValue(row, null), null);
        }

        protected virtual bool CopyToProperty(object output, Type typeOutput, System.Reflection.PropertyInfo propOutput)
        {
            return false; // Override to provide custom conversion
        }

        public static TOutput Convert<TOutput, TInput>(TInput input)
            where TOutput : new()
        {
            var output = new TOutput();
            // TODO: Use SecUtility.Check on everything (#if DEBUG)
            // Eg: SecUtility.CheckParameter(ref caption, false, false, false, TableStorageConstants.MaxStringPropertySizeInChars, "caption");
            var typeOutput = typeof(TOutput);
            var typeInput = typeof(TInput);
            foreach (var propOutput in typeOutput.GetProperties())
                if (propOutput.CanWrite)
                {
                    var propInput = typeInput.GetProperty(propOutput.Name);
                    if (propInput == null)
                    {
                        if (typeOutput.IsSubclassOf(typeof(Row)) && (propOutput.Name == "Timestamp"
                            || propOutput.Name == "PartitionKey" || propOutput.Name == "RowKey"))
                            continue; // Ignore
                        var row = input as Row;
                        if (row != null && row.CopyToProperty(output, typeOutput, propOutput))
                            continue; // Ignore
                        return Log.LogErrorAndThrowException<TOutput>(
                            new Exception("Cannot the get value of " + typeInput.FullName + "." + propOutput.Name),
                            "Row.Convert(): Cannot the get value of " + typeInput.FullName + "." + propOutput.Name);
                    }
                    var valInput = propInput.GetValue(input, null);
                    var valOutput = ConvertProperty(valInput, propInput.PropertyType, propOutput.PropertyType);
                    propOutput.SetValue(output, valOutput, null);
                }

            var businessObject = output as IBusinessBase;
            if (businessObject != null)
                businessObject.MarkOld();
            return output;
        }

        private static object ConvertProperty(object valInput, Type inputType, Type outputType)
        {
            if (valInput == null)
            {
                if (outputType == typeof(string))
                    return string.Empty;
                if (outputType == typeof(Guid))
                    return Guid.Empty;
                return Log.LogErrorAndThrowException(new Exception("Do not have the default value of " + outputType.FullName),
                    "Row.ConvertProperty(): Do not have the default value of " + outputType.FullName);
            }
            if (inputType == outputType)
                return valInput;

            if (inputType == typeof(double) && outputType == typeof(float))
                return (float)(double)valInput;
            if (inputType == typeof(float) && outputType == typeof(double))
                return (double)(float)valInput;

            if (inputType == typeof(Guid) && outputType == typeof(Guid?))
                return (Guid?)valInput;
            if (inputType == typeof(Guid?) && outputType == typeof(Guid))
                return (Guid)valInput;

            if (inputType == typeof(Uri) && outputType == typeof(string))
                return ((Uri)valInput).AbsoluteUri;
            if (inputType == typeof(string) && outputType == typeof(Uri))
                return new Uri(((string)valInput));

            return Log.LogErrorAndThrowException(new Exception("Cannot convert from " + inputType.FullName + " to " + outputType.FullName),
                "Row.ConvertProperty(): Cannot convert from " + inputType.FullName + " to " + outputType.FullName);
        }
    }



    public abstract class BusinessBase : Row
    {
        public Guid Id { get; set; }
        public DateTime DateCreated { get; set; }
        public DateTime DateModified { get; set; }

        protected BusinessBase()
        {
            Id = Guid.NewGuid();
            DateCreated = DateModified = DateTime.Now;
        }

        public override string GetRowKey()
        {
            return ToRowKey(Id);
        }
    }


    public class BlogRollItem : BusinessBase
    {
        public string Title { get; set; }
        public string Description { get; set; }
        public string FeedUrl { get; set; }
        public string BlogUrl { get; set; }
        public string Xfn { get; set; }
        public int SortIndex { get; set; }
    }

    public class Category : BusinessBase
    {
        public string Title { get; set; }
        public string Description { get; set; }
        public Guid Parent { get; set; }
    }

    public class Comment : PostRelatedRow
    {
        public Guid Id { get; set; }
        public string Author { get; set; }
        public string Email { get; set; }
        public string Website { get; set; }
        public string Content { get; set; }
        public string Country { get; set; }
        public string IP { get; set; }
        public bool IsApproved { get; set; }
        public DateTime DateCreated { get; set; }

        public Comment()
        {
            Id = Guid.NewGuid();
        }

        public override string GetSubRowKey()
        {
            return ToRowKey(Id);
        }

        protected override bool CopyToProperty(object output, Type typeOutput, System.Reflection.PropertyInfo propOutput)
        {
            if (typeOutput == typeof(Core.Comment) && propOutput.Name == "Parent")
                return true; // Ignore; will be set by the BlogProvider
            return false;
        }
    }

    public class DataStoreSetting : Row
    {
        public int ExtensionType { get; set; }
        public string ExtensionId { get; set; }
        public string Setting { get; set; }

        public override string GetRowKey()
        {
            return ExtensionId;
        }
    }

    public class Page : BusinessBase
    {
        public string Title { get; set; }
        public string Content { get; set; }
        public string Description { get; set; }
        public string Keywords { get; set; }
        public Guid Parent { get; set; }
        public string Slug { get; set; }
        public bool IsPublished { get; set; }
        public bool IsFrontPage { get; set; }
        public bool ShowInList { get; set; }
    }

    public class PingService : Row
    {
        public string Url { get; set; }

        public override string GetRowKey()
        {
            return System.Web.HttpUtility.UrlEncode(Url); // TODO: Why aren't some characters allowed?
        }
    }

    public class Post : BusinessBase
    {
        public string Author { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string Content { get; set; }
        public bool IsCommentsEnabled { get; set; }
        public bool IsPublished { get; set; }
        public double Rating { get; set; }
        public int Raters { get; set; }
        public string Slug { get; set; }
    }

    public abstract class PostRelatedRow : Row
    {
        public Guid ParentId { get; set; } // Note: This name cannot change because it must match Comment.ParentId in BlogEngine.Core

        public override string GetRowKey()
        {
            return ToRowKey(ParentId) + GetSubRowKey(); // This allows a possible optimization using prefix when selecting by RowKey (ParentId<=RowKey && RowKey<ParentId+1)
        }
        public abstract string GetSubRowKey();
    }

    public class PostCategory : PostRelatedRow
    {
        public Guid CategoryId { get; set; }

        public override string GetSubRowKey()
        {
            return ToRowKey(CategoryId);
        }
    }

    public class PostNotification : PostRelatedRow
    {
        public string Email { get; set; }

        public override string GetSubRowKey()
        {
            return Email;
        }
    }

    public class PostTag : PostRelatedRow
    {
        public string Tag { get; set; }

        public override string GetSubRowKey()
        {
            return Tag;
        }
    }

    public class Referrer : BusinessBase
    {
        public string ReferrerUrl { get; set; }
        public string Url { get; set; }
        public int Count { get; set; }
        public DateTime Day { get; set; }
        public bool PossibleSpam { get; set; }
    }

    public class Setting : Row
    {
        public string Name { get; set; }
        public string Value { get; set; }

        public override string GetRowKey()
        {
            return Name;
        }
    }

    public class StopWord : Row
    {
        public string Value { get; set; }

        public override string GetRowKey()
        {
            return Value;
        }
    }
}